热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

可能会|也就是_单例设计模式你真的会用吗?

篇首语:本文由编程笔记#小编为大家整理,主要介绍了单例设计模式你真的会用吗?相关的知识,希望对你有一定的参考价值。1

篇首语:本文由编程笔记#小编为大家整理,主要介绍了单例设计模式你真的会用吗?相关的知识,希望对你有一定的参考价值。




1 快速入门

应用场景:一些重量级的对象(如:数据库连接池)不需要多个实例

实现方式如下:



  • 懒汉模式

    需要用到时采取创建实例



  • 饿汉模式

    类加载的初始化阶段去创建



  • 静态内部类

    类加载的初始化阶段去创建+懒汉模式




1.1 懒汉模式

示例代码如下

import lombok.Data;
/**
* 懒汉模式实现单例
* @author sai
* @version 1.0
* @date 2022/3/27 14:55
*/
@Data
public class LazySingleModel
private Integer param1 = 1;
private String param2 = "1";
/**
* 使用 volatile 禁止指令重排,防止并发场景下出现空指针错误
*/
private volatile static LazySingleModel instance;
private LazySingleModel()
/**
* 获取实例方法
*/
public static LazySingleModel getInstance()
if (null == instance)
synchronized (LazySingleModel.class)
// 使用 double check 防止多线程造成重复创建多个实例
if (null == instance)
instance = new LazySingleModel();
return instance;



return instance;

public static void main(String[] args)
LazySingleModel singleModel = LazySingleModel.getInstance();
// 如果不加 volatile,在高并发场景下,下面这行代码可能会出现空指针异常
String param1 = singleModel.getParam1().toString();
System.out.println(param1 + singleModel.getParam2());

优点:



  • 用到时才去加载

缺点:



  • 第一次去创建单例时遇到了并发较高的话,性能较差

  • 要自己保证线程安全


1.2 饿汉模式

示例代码如下

/**
* 饿汉模式
* @author sai
* @version 1.0
* @date 2022/3/27 15:07
*/
public class HungerSingleModel
private static final HungerSingleModel INSTANCE = new HungerSingleModel();
private HungerSingleModel()
public static HungerSingleModel getInstance()
return INSTANCE;

public static void main(String[] args)
HungerSingleModel instance1 = HungerSingleModel.getInstance();
HungerSingleModel instance2 = HungerSingleModel.getInstance();
System.out.println(instance1 == instance2);

优点:



  • 类加载完成时就已经创建好单例对象了,就算遇到高并发请求也没事

  • 实现简单

缺点:



  • 有时我们 JVM 加载这个类时,不一定是要用到实例对象的,那么创建单例和类加载强捆绑一起也不是那么的合适


1.3 静态内部类实现

示例代码如下

/**
* 静态内部类
* @author sai
* @version 1.0
* @date 2022/3/27 15:23
*/
public class InnerClassSingleModel
private InnerClassSingleModel()

static class InnerClassSingleModelHandle
private static final InnerClassSingleModel INSTANCE = new InnerClassSingleModel();

public static InnerClassSingleModel getInstance()
return InnerClassSingleModelHandle.INSTANCE;

public static void main(String[] args)
// 懒加载:第一次调用获取单例方法时才会去加载 InnerClassSingleModelHandle,这时才创建 InnerClassSingleModel 的实例对象
// ps:JVM 类加载机制也是采用懒加载方式的
InnerClassSingleModel instance = InnerClassSingleModel.getInstance();
System.out.println(instance);

就一般而言,我们写单例时比较推荐这种实现方式,因为它具有懒汉模式和饥汉模式的优点。


2 上面所有的示例代码居然是有问题的!?

下面均用静态内部类的示例代码为例子说明问题所在,以及如何修改


2.1 反射问题

证明问题所在,具体代码如下

public class InnerClassSingleModel
...
public static void main(String[] args) throws Exception
Constructor cOnstructor= InnerClassSingleModel.class.getDeclaredConstructor();
InnerClassSingleModel instance1 = constructor.newInstance();
InnerClassSingleModel instance2 = InnerClassSingleModel.getInstance();
System.out.println(instance1 == instance2);

运行结果如下

false 说明的确可以通过反射来绕过单例。

修复代码如下

import java.lang.reflect.Constructor;
/**
* 静态内部类
* @author sai
* @version 1.0
* @date 2022/3/27 15:23
*/
public class InnerClassSingleModel
private InnerClassSingleModel()
if (null != InnerClassSingleModelHandle.INSTANCE)
// 因为反射创建实例一样会走构建方法,所以可以在构建方法里面加校验逻辑
throw new RuntimeException("单例模式不允许通过反射创建实例对象");


static class InnerClassSingleModelHandle
private static final InnerClassSingleModel INSTANCE = new InnerClassSingleModel();

public static InnerClassSingleModel getInstance()
return InnerClassSingleModelHandle.INSTANCE;

public static void main(String[] args) throws Exception
Constructor cOnstructor= InnerClassSingleModel.class.getDeclaredConstructor();
InnerClassSingleModel instance1 = constructor.newInstance();
InnerClassSingleModel instance2 = InnerClassSingleModel.getInstance();
System.out.println(instance1 == instance2);


2.2 序列化问题

先还原问题,步骤如下:

1、先将类序列化到磁盘,代码如下

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 静态内部类
* @author sai
* @version 1.0
* @date 2022/3/27 15:23
*/
public class InnerClassSingleModel implements Serializable
private InnerClassSingleModel()
if (null != InnerClassSingleModelHandle.INSTANCE)
// 因为反射创建实例一样会走构建方法,所以可以在构建方法里面加校验逻辑
throw new RuntimeException("单例模式不允许通过反射创建实例对象");


static class InnerClassSingleModelHandle
private static final InnerClassSingleModel INSTANCE = new InnerClassSingleModel();

public static InnerClassSingleModel getInstance()
return InnerClassSingleModelHandle.INSTANCE;

public static void main(String[] args) throws Exception
InnerClassSingleModel instance = InnerClassSingleModel.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable"));
oos.writeObject(instance);

生成了一个序列化文件,如下图

2、通过序列化文件读取类的字节流,生成对象还原问题

public class InnerClassSingleModel implements Serializable
...
public static void main(String[] args) throws Exception
InnerClassSingleModel instance = InnerClassSingleModel.getInstance();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
InnerClassSingleModel instance2 = (InnerClassSingleModel) ois.readObject();
System.out.println(instance == instance2);

运行结果如下

修复代码如下

package com.czl.java.demo.designmodel;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
/**
* 静态内部类
* @author sai
* @version 1.0
* @date 2022/3/27 15:23
*/
public class InnerClassSingleModel implements Serializable
/**
* 这个是为了兼容版本使用。如现在我有两个成员变量,日后我这个类被修改了,也能做兼容
* 举个例子:如我现在有 param1 和 param2,我序列化好了。回头我删掉一个字段或者增加个字段,有 serialVersionUID 的话反序列化时一样能够兼容,
* 否则会抛异常
*/
private static final long serialVersiOnUID= -3511142029087501481L;
/**
* 这两个字段只是为了说明 serialVersionUID 的作用
*/
private Integer param1 = 1;
private String param2 = "1";
public Integer getParam1()
return param1;

public void setParam1(Integer param1)
this.param1 = param1;

public String getParam2()
return param2;

public void setParam2(String param2)
this.param2 = param2;

/**
* 解决反序列化绕开单例问题,需要增加这个方法,修饰符可以自定义
*/
private Object readResolve()
return getInstance();

private InnerClassSingleModel()
if (null != InnerClassSingleModelHandle.INSTANCE)
// 因为反射创建实例一样会走构建方法,所以可以在构建方法里面加校验逻辑
throw new RuntimeException("单例模式不允许通过反射创建实例对象");


static class InnerClassSingleModelHandle
private static final InnerClassSingleModel INSTANCE = new InnerClassSingleModel();

public static InnerClassSingleModel getInstance()
return InnerClassSingleModelHandle.INSTANCE;

public static void main(String[] args) throws Exception
InnerClassSingleModel instance = InnerClassSingleModel.getInstance();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
InnerClassSingleModel instance2 = (InnerClassSingleModel) ois.readObject();
System.out.println(instance == instance2);


推荐阅读
author-avatar
caozhizhao
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有